﻿package  
{
	import flash.geom.Point;
	import Util.*;
	import de.polygonal.math.PM_PRNG;
	
	/**
	 * ...
	 * @author Lissa Avery
	 */
	public class World 
	{
		private var sectors:Array;
		private var treasures:Array;
		private var seed:int;
		private var rndm:PM_PRNG;
		private var width:int;
		private var height:int;
		private var numMountainRanges:int;
		private static var world:World;
		private static var allowInstantiation:Boolean;
		
		//private static const INITIAL_LAND_MASSES:int = 5;
		private static const INITIAL_LAND_MASSES:int = 35;
		private static const GROWTH_ITERATIONS:int = 100;
		private static const NUM_MOUNTAINS:int = 10;
		
		public function World() 
		{
			
			if (!allowInstantiation) {
				throw new Error("Please use getWorld instead of new.");
			}
			//width = new int(Preloader.STAGE_WIDTH / 64);
			//height = new int(Preloader.STAGE_HEIGHT / 64);
			width = 25;
			height = 25;
			seed = new int(1);
			rndm = new PM_PRNG();
			rndm.seed = seed;
			numMountainRanges = rndm.nextIntRange(0, INITIAL_LAND_MASSES);
		}
		
		public function getBiome(x:int, y:int):int {
			return sectors[x][y].biome;			
		}
		
		public function getSector(x:int, y:int):Sector {
			return sectors[x][y];			
		}
		
		private function generateEvidence(): void {
			
		}
		
		public function getEvidence(x:int, y:int):void {
			
		}
		
		static public function getWorld():World {
			if (world == null) {
				allowInstantiation = true;
				world = new World();
				world.generatePlanet();
				allowInstantiation = false;
			}
			
			return world;
		}
		
		public function getSeed():int {
			return seed;
		}
		
		private function generatePlanet():void {

			// The entire planet.  Note that we only need half the number of spaces as 
			// there are given by the resolution, courtesty of the method used for 
			// interpolating the boundaries between regions.
			
			/* Each location on Namatjira will have the following pieces of information:

			0: Altitude (generated with Perlin noise, from 0 to 2)
			1: Temperature (Celsius)
			2: Humitidy (float from 0 to 1)
			3: Civilization density (float from 0 to 1)
			4: Biome (0 - 3)
			*/  
			
			sectors = new Array(this.width);	
			
			for (var i:int = 0; i < this.width; i += 1)
			{
				sectors[i] = new Array(this.height);
				for (var j:int = 0; j < this.height; j += 1)
				{
					sectors[i][j] = new Sector(this, i, j, Biome.WATER);
				}
			}
			
			
			generateLandMasses();
			generateMountains();
					
		}
		
		private function generateLandMasses():void {
			var coastalTiles:Array = new Array();
			
			initializeLandMasses(coastalTiles);
			growLandMasses(coastalTiles);
		}
		
		private function generateMountains():void {
			//var mtnWeights:Array = new Array();
			calculateWaterDistances();
			var totalWeights:int = 0;
			var landTiles:Array = getLandSectors();
			
			for (var i:int = 0; i < landTiles.length; i += 1)
			{
				if (landTiles[i].biome != Biome.WATER) {
					totalWeights += landTiles[i].distanceToWater;
				}
			}
			
			var k:int = 0;
			while(k <= NUM_MOUNTAINS) {
				var randLocation:int = rndm.nextIntRange(0, totalWeights);
				//trace("randLocation = " + randLocation);				
				
				var m:int = -1;
				while (randLocation > 0) {
					//trace("dist to water = " + sectors[x][y].distanceToWater);
					randLocation = randLocation - landTiles[++m].distanceToWater;
					//trace("randLocation = " + randLocation);
					
				}
				
				//trace("biome = " + sectors[x][y].biome)
				landTiles[m].altitude = 1;
				landTiles[m].biome = Biome.MOUNTAINS;
				k++;
				
				//trace("i = " + i);
			}
		}
		
		private function getLandSectors():Array {
			var landTiles:Array = new Array();
			
			for (var i:int = 0; i < this.width; i += 1)
			{
				for (var j:int = 0; j < this.height; j += 1)
				{
					if (sectors[i][j].biome != Biome.WATER) {
						landTiles.push(sectors[i][j]);
					}
				}
			}
			
			return landTiles;
		}
		
		private function calculateWaterDistances():void {
			for (var i:int = 0; i < this.width; i += 1)
			{
				for (var j:int = 0; j < this.height; j += 1)
				{
					if (sectors[i][j].biome != Biome.WATER) {
						if (sectors[i][j].distanceToWater == 0) {
							setDistanceToWater(i, j);
						}
					}
				}
			}
		}
		
		private function initializeLandMasses(coastalTiles:Array):void {
			var initalLandPoints:Array = new Array(INITIAL_LAND_MASSES);
			
			for (var i:int = 0; i < INITIAL_LAND_MASSES; i++) {
				var x:int = rndm.nextIntRange(0, width - 1);
				var y:int = rndm.nextIntRange(0, height - 1);
				
				sectors[x][y].biome = Biome.PLAINS;
				sectors[x][y].altitude = 1.0;
				coastalTiles.push(new Point(x, y));
			}
		}
		
		private function growLandMasses(coastalTiles:Array):void {
			//trace("initial spots: " + coastalTiles);
			for (var i:int = 0; i < GROWTH_ITERATIONS; i ++) {
				var rndPt:int = rndm.nextIntRange(0, coastalTiles.length - 1);
				while (getNumberEmptyAdjacent(coastalTiles[rndPt]) == 0) {
					coastalTiles.splice(rndPt, 1);
					rndPt = rndm.nextIntRange(0, coastalTiles.length - 1);
				}
				//trace("random " + i + " : " + rndPt);
				//trace("Coastal pt: " + coastalTiles[rndPt]);
				
				growLandMass(coastalTiles, rndPt);
				
				//trace("Coastals: " + coastalTiles);
			}
			//trace("Coastals: " + coastalTiles);
		}
		
		private function growLandMass(coastalTiles:Array, coastalIndex:int):void {
			//trace(coastalTiles[coastalIndex]);
			var numberEmptyAdjacent:int = getNumberEmptyAdjacent(coastalTiles[coastalIndex]);
			//trace("number empty adj: " + numberEmptyAdjacent);
			var emptyAdjacent:Point = getEmptyAdjacent(coastalTiles[coastalIndex], numberEmptyAdjacent);
			sectors[emptyAdjacent.x][emptyAdjacent.y].biome = Biome.PLAINS;
			sectors[emptyAdjacent.x][emptyAdjacent.y].altitude = 1.0;
			
			if (getNumberEmptyAdjacent(coastalTiles[coastalIndex]) == 0) {
				//trace("removing " + coastalTiles[coastalIndex] + " from the coastal tiles.");
				coastalTiles.splice(coastalIndex, 1);
			}
			if (getNumberEmptyAdjacent(emptyAdjacent) != 0) {
				coastalTiles.push(emptyAdjacent);
			}
			
		}
		
		private function getEmptyAdjacent(landPoint:Point, numberEmptyAdjacent:int):Point {
			//trace("num adj - 1: " + (numberEmptyAdjacent - 1));
			var randEmpty:int = rndm.nextIntRange(0, numberEmptyAdjacent - 1);
			var prevX:int = (landPoint.x + width - 1) % width;
			var nextX:int = (landPoint.x + width + 1) % width;
			var prevY:int = (landPoint.y + height - 1) % height;
			var nextY:int = (landPoint.y + height + 1) % height;
			//trace("rand empty:" + randEmpty);
			
			switch (randEmpty) {
				case 0:
					if (sectors[nextX][landPoint.y].biome == Biome.WATER) {
						return new Point(nextX, landPoint.y);
					}
				case 1:
					if (sectors[landPoint.x][prevY].biome == Biome.WATER) {
						return new Point(landPoint.x, prevY);	
					}
				case 2:
					if (sectors[landPoint.x][nextY].biome == Biome.WATER) {
						return new Point(landPoint.x, nextY);
					}
				case 3:
					if (sectors[prevX][landPoint.y].biome == Biome.WATER) {
						return new Point(prevX, landPoint.y);
					}
				default:
					//trace("randEmpty: " + randEmpty);
					return new Point(nextX, nextY);
			}
		}
		
		private function getNumberEmptyAdjacent(landPoint:Point):int {
			var numberEmptyAdjacent:int = 0;
			
			var prevX:int = (landPoint.x + width - 1) % width;
			var nextX:int = (landPoint.x + width + 1) % width;
			var prevY:int = (landPoint.y + height - 1) % height;
			var nextY:int = (landPoint.y + height + 1) % height;
			
			if (sectors[landPoint.x][nextY].biome == Biome.WATER) {
				numberEmptyAdjacent ++;
			}
			if (sectors[landPoint.x][prevY].biome == Biome.WATER) {
				numberEmptyAdjacent ++;
			}
			if (sectors[nextX][landPoint.y].biome == Biome.WATER) {
				numberEmptyAdjacent ++;
			}
			if (sectors[prevX][landPoint.y].biome == Biome.WATER) {
				numberEmptyAdjacent ++;
			}
			
			return numberEmptyAdjacent;
		}

		public function getHeight():int {
			return height;
		}
		
		public function getWidth():int {
			return width;
		}
		
		private function setDistanceToWater(x:int, y:int):void {
			var leastDistance:int = this.height;
			
			for (var dir:int = 0; dir < 8; dir ++) {
				var dist:int = getDistanceToWaterInDirection(x, y, dir, 0);
				if (dist < leastDistance) {
					leastDistance = dist;
					//trace("lowest dist is now: " + leastDistance);
				}
			}
			
			sectors[x][y].distanceToWater = leastDistance;
		}
		
		private function getDistanceToWaterInDirection(x:int, y:int, dir:int, distance:int = 0):int {
			var prevX:int = (x + width - 1) % width;
			var nextX:int = (x + width + 1) % width;
			var prevY:int = (y + height - 1) % height;
			var nextY:int = (y + height + 1) % height;
			
			switch (dir) {
				case 0:
					// Up
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(x, prevY, dir, distance + 1);
					}
				case 1:
					// Up and to the right
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(nextX, prevY, dir, distance + 1);
					}
				case 2:
					// Right
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(nextX, y, dir, distance + 1);
					}
				case 3:
					// Right and down
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(nextX,nextY, dir, distance + 1);
					}
				case 4:
					// Down
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(x, nextY, dir, distance + 1);
					}
				case 5:
					// Down and left
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(prevX, nextY, dir, distance + 1);
					}
				case 6:
					// Left
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(prevX, y, dir, distance + 1);
					}
				case 7:
					// Left and up
					if (sectors[x][y].biome == Biome.WATER) {
						return distance;
					} else {
						return getDistanceToWaterInDirection(prevX, prevY, dir, distance + 1);
					}
			}
			
			return 0;
		}
		
	}
	
}